//
//  StopWatch.m
//  ChimooTimer
//
//  Created by Ryan on Mon Sep 29 2003.
//  Chimoosoft (c) 2003-2007 Chimoosoft. All rights reserved.
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.

#import "StopWatch.h"

@interface StopWatch ()

- (void)createTimer;
- (void)destroyTimer;

@end


@implementation StopWatch

const float INTERNAL_REFRESH_RATE = 0.01;
const float FASTEST_REFRESH_RATE = 0.05;
const float SLOWEST_REFRESH_RATE = 30;

+ (double)nowInMilliseconds {
	UInt64 result = 0;
	Nanoseconds nanosec = AbsoluteToNanoseconds(UpTime());
	return (double)UnsignedWideToUInt64(nanosec)/1000000.0;
}

- (id)init {
	
	if (self = [super init]) {
		mode = UP;				// count up mode by default.
		startTimeInMilliseconds = 0.0;
		stopTimeInMilliseconds = 0.0;
		countDownMilliseconds = 0.0;
		userSelectedCountDownInMilliseconds = 0.0;
		displayUpdateMilliseconds = 200.0;
		
		lastIntSecond = 0;
		lastIntMinute = 0;
		lastIntHour = 0;
		
		running = NO;
		
		//[NSThread detachNewThreadSelector:@selector(createThread:) toTarget:self withObject:nil];
	}
	
	return self;
}

/*
- (void)createThread:(id)param {
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

	NSLog(@"hi");
	
	[pool release];
}
*/

// Set the receiver's delegate to be aDelegate.
- (void)setDelegate:(id)aDelegate {
	// note, we don't want to retain this.  See 
	// http://cocoadevcentral.com/articles/000075.php for more info on this
	
	delegate = aDelegate;
	
	if (nil == delegate) return;

	if (![delegate respondsToSelector:@selector(stopWatchDidUpdate:)]) {
		delegate = nil;
		[NSException raise:NSInternalInconsistencyException
					format:@"Delegate doesn't respond to stopWatchDidUpdate:(StopWatch*)watch."];
	}
	
	if (![delegate respondsToSelector:@selector(stopWatchDidStart:)]) {
		delegate = nil;
		[NSException raise:NSInternalInconsistencyException
					format:@"Delegate doesn't respond to stopWatchDidStart:(StopWatch*)watch."];
	}

	if (![delegate respondsToSelector:@selector(stopWatchDidStop:)]) {
		delegate = nil;
		[NSException raise:NSInternalInconsistencyException
					format:@"Delegate doesn't respond to stopWatchDidStart:(StopWatch*)watch."];
	}
	
	if (![delegate respondsToSelector:@selector(stopWatchDidExpire:)]) {
		delegate = nil;
		[NSException raise:NSInternalInconsistencyException
					format:@"Delegate doesn't respond to stopWatchDidExpire:(StopWatch*)watch."];
	}

	if (![delegate respondsToSelector:@selector(stopWatchDidReset:)]) {
		delegate = nil;
		[NSException raise:NSInternalInconsistencyException
					format:@"Delegate doesn't respond to stopWatchDidReset:(StopWatch*)watch."];
	}
	
	if (![delegate respondsToSelector:@selector(stopWatchDidTickSecond:)]) {
		delegate = nil;
		[NSException raise:NSInternalInconsistencyException
					format:@"Delegate doesn't respond to stopWatchDidTickSecond:(StopWatch*)watch."];
	}

	if (![delegate respondsToSelector:@selector(stopWatchDidTickMinute:)]) {
		delegate = nil;
		[NSException raise:NSInternalInconsistencyException
					format:@"Delegate doesn't respond to stopWatchDidTickMinute:(StopWatch*)watch."];
	}

	if (![delegate respondsToSelector:@selector(stopWatchDidTickHour:)]) {
		delegate = nil;
		[NSException raise:NSInternalInconsistencyException
					format:@"Delegate doesn't respond to stopWatchDidTickHour:(StopWatch*)watch."];
	}
	
}

- (void)toggleCountDirection {
	if (mode == DOWN) [self enterCountUpMode];
	else ([self enterCountDownMode]);
}

- (void)enterCountDownMode { 
	if (mode != DOWN) {
		// Stopwatch was in count up mode.
		countDownMilliseconds = [self elapsedTimeInMilliseconds];
		if ([self isRunning]) startTimeInMilliseconds = [StopWatch nowInMilliseconds];
		else startTimeInMilliseconds = stopTimeInMilliseconds;
	}
	
	mode = DOWN;
}

- (void)enterCountUpMode {
	if (mode != UP) {
		// Stopwatch was in count down mode.
		if ([self isRunning]) startTimeInMilliseconds = [StopWatch nowInMilliseconds] - [self elapsedTimeInMilliseconds];
		else startTimeInMilliseconds = stopTimeInMilliseconds - [self elapsedTimeInMilliseconds];
	}
	
	mode = UP;	
}

- (void)start {
	// record these right away to get the best accuracy
	double startTime = [StopWatch nowInMilliseconds];
	double elapsedTime = [self elapsedTimeInMilliseconds];
	
	if ([self isRunning]) return;
	
	double offset = 0.0;
	
	if (mode == UP) {
		// starting in count up mode
		offset = -1.0*elapsedTime;
	} else {
		// starting in count down mode
		countDownMilliseconds = elapsedTime;
	}
	
	startTimeInMilliseconds = startTime + offset;
	
	// create the timer
	[self createTimer];
	running = YES;
	[delegate stopWatchDidStart:self];
}

- (void)stop {
	double stopTime = [StopWatch nowInMilliseconds];
	if (![self isRunning]) return;

	stopTimeInMilliseconds = stopTime;	
	
	[self destroyTimer];
	running = NO;

	[delegate stopWatchDidStop:self];
}

- (void)toggleStartStop {
	if ([self isRunning]) [self stop];
	else [self start];
}

- (void)reset {
	stopTimeInMilliseconds = startTimeInMilliseconds = 0.0;

	if ([self isRunning]) startTimeInMilliseconds = [StopWatch nowInMilliseconds];
	
	if (mode == DOWN) countDownMilliseconds = userSelectedCountDownInMilliseconds;
	
	[delegate stopWatchDidReset:self];
}


- (void)resumeTransparentlyFromLastStop {
	if ([self isRunning]) return;
	
	stopTimeInMilliseconds = [StopWatch nowInMilliseconds];
	[self start];
}

- (void)setRefreshRate:(float)rate {
	float newRate = fabsf(rate);
	
	if (newRate < FASTEST_REFRESH_RATE) newRate = FASTEST_REFRESH_RATE;
	if (newRate > SLOWEST_REFRESH_RATE) newRate = SLOWEST_REFRESH_RATE;

	displayUpdateMilliseconds = 1000.0 * newRate;
}


- (void)setUserCountDownMilliseconds:(double)milli {
	userSelectedCountDownInMilliseconds = fabs(milli);
}

- (void)setUserCountDownSeconds:(double)seconds {
	userSelectedCountDownInMilliseconds = fabs(1000.0 * seconds);
}

- (double)userCountDownSeconds {
	return (userSelectedCountDownInMilliseconds / 1000.0);
}

- (double)userCountDownMilliseconds {
	return userSelectedCountDownInMilliseconds;
}

- (double)countingDownUntilInSeconds {
	return countDownMilliseconds / 1000.0;
}

- (BOOL)isRunning {
	return running;
}

- (BOOL)isInCountDownMode {
	return (mode == DOWN);
}

- (double)totalSeconds {
	return [StopWatch nowInMilliseconds] - startTimeInMilliseconds;
}

// sets the passed parameters to the current HMS format.
- (void)hours:(out int *)h minutes:(out int *)m seconds:(out double*)s {
	double t = [self elapsedTimeInSeconds];
	*h = (t / 3600);
	*m = (t - (3600 * (*h))) / 60;
	*s = (t - (3600 * (*h)) - (60 * (*m)));	
}


- (void)destroyTimer {
	[timer invalidate];
	[timer release];
	timer = nil;	
}


- (void)createTimer {
	[self destroyTimer];

	timer = [[NSTimer scheduledTimerWithTimeInterval:INTERNAL_REFRESH_RATE
													 target:self
												   selector:@selector(timerUpdate:)
												   userInfo:nil
													repeats:YES] retain];
	
}


- (double)elapsedTimeInMilliseconds {
	double now = [StopWatch nowInMilliseconds];
	if (![self isRunning]) now = stopTimeInMilliseconds;
	
	if (mode == UP) {
		return now - startTimeInMilliseconds;
	} else {
		// count down
	//	double d = countDownMilliseconds - (now - startTimeInMilliseconds);
	//	NSLog(@"%f", d/1000.0);
	//	return d;
		return countDownMilliseconds - (now - startTimeInMilliseconds);
	}
}

- (double)elapsedTimeInSeconds {
	return ([self elapsedTimeInMilliseconds] / 1000.0);
}

- (NSNumber*)elapsedTimeInSecondsAsNumber {
	return [NSNumber numberWithDouble:[self elapsedTimeInSeconds]];
}



// Called every time the main timer fires.  The main complication is if the timer 
// is in count down mode.  Need to catch it accurately when it should expire.
- (void)timerUpdate:(NSTimer*)theTimer {
	double time = [self elapsedTimeInMilliseconds];
	
	if (mode == DOWN) {
		if (time <= 0.0) {
			// countdown is already over
			[self stop];
			[delegate stopWatchDidExpire:self];
			[self reset];
		} 
	}
	
	double now = [StopWatch nowInMilliseconds];
	
	if ((now - lastUpdateNotificationMilliseconds) > displayUpdateMilliseconds) {
		// time to send an update notification
		[delegate stopWatchDidUpdate:self];
		lastUpdateNotificationMilliseconds = now;
	}
	
	int sec = floor(time / 1000.0);
	if (lastIntSecond != sec) {
		lastIntSecond = sec;
		
		int min = floor(sec / 60.0);
		if (lastIntMinute != min) {
			lastIntMinute = min;
			
			int hour = floor(min / 60.0);
			if (lastIntHour != hour) {
				lastIntHour = hour;
				[delegate stopWatchDidTickHour:self];	
			}
			
			[delegate stopWatchDidTickMinute:self];
		}
		
		[delegate stopWatchDidTickSecond:self];
	}
		

}


- (void)dealloc {
	[self destroyTimer];
	
	[super dealloc];
}


@end


